package network

import (
	"fmt"
	"github.com/Sirupsen/logrus"
	"github.com/vishvananda/netlink"
	"net"
	"os/exec"
	"strings"
	"time"
)

// 需要打开网络栈的三层转发功能  命令行:
// sysctl net.ipv4.conf.all.forwarding=1

// BridgeNetworkDriver 创建虚拟网桥bridge和挂载veth设备
type BridgeNetworkDriver struct {
}

func (this *BridgeNetworkDriver) Name() string {
	return "bridge"
}

func (this *BridgeNetworkDriver) Create(subnet string, name string) (*Network, error) {
	// 通过net包中的net.ParseCIDR方法, 取到网段的字符串中的网关ip地址和网络ip段
	ip, ipRange, _ := net.ParseCIDR(subnet)
	// 网关ip
	ipRange.IP = ip
	// 初始化网络对象
	networkPtr := &Network{
		Name:       name,
		IpRangePtr: ipRange,
		Driver:     this.Name(),
	}
	// 配置linux bridge
	err := this.initBridge(networkPtr)
	if err != nil {
		logrus.Errorf("error init bridge: %v", err)
	}

	return networkPtr, err
}

// Delete 删除网络bridge设备
func (this *BridgeNetworkDriver) Delete(network Network) error {
	// 网络名即bridge的设备名
	bridgeName := network.Name
	// 通过netlink.LinkByName找到网络对应的设备
	brLink, err := netlink.LinkByName(bridgeName)
	if err != nil {
		return err
	}
	// 删除网络对应的bridge设置
	return netlink.LinkDel(brLink)
}

// Connect 连接容器到之前创建的网络, 使容器加入网络
func (this *BridgeNetworkDriver) Connect(networkPtr *Network, endpointPtr *Endpoint) error {
	// 获取网络名, 即bridge的接口名
	bridgeName := networkPtr.Name
	// 通过接口名获取到bridge接口的对象和接口属性
	br, err := netlink.LinkByName(bridgeName)
	if err != nil {
		return err
	}

	// ip link add veth0 type veth peer name veth1
	// brctl addif br0 veth0
	// 创建veth接口的配置
	la := netlink.NewLinkAttrs()
	// 由于linux接口名的限制, 取端点 ID的前5位
	la.Name = endpointPtr.ID[:5]
	// 通过设置veth接口的master属性, 设置这个veth的一端挂载到网络对应的bridge上
	la.MasterIndex = br.Attrs().Index

	// 创建veth对象,通过peerName配置veth另外一端的接口名
	// 配置veth另外一端的名字cif-{endpoint ID的前5位}
	endpointPtr.Device = netlink.Veth{
		LinkAttrs: la,
		PeerName:  "cif-" + endpointPtr.ID[:5],
	}
	// 调用netlink.LinkAdd方法创建出这个veth接口
	// 因为上面指定了link的masterIndex(是网络对应的bridge),所以veth的一端就已经挂载到了网络对应的bridge上
	if err = netlink.LinkAdd(&endpointPtr.Device); err != nil {
		return fmt.Errorf("Error Add Endpoint Device: %v", err)
	}

	// 调用netlink.LinkSetUp方法,设置veth启动
	// 相当于 ip link set veth0 up命令
	if err = netlink.LinkSetUp(&endpointPtr.Device); err != nil {
		return fmt.Errorf("Error Add Endpoint Device: %v", err)
	}
	return nil
}

func (this *BridgeNetworkDriver) Disconnect(network Network, endpointPtr *Endpoint) error {
	return nil
}

// 初始化网桥
func (this *BridgeNetworkDriver) initBridge(networkPtr *Network) error {
	bridgeName := networkPtr.Name
	// try to get bridge by name, if it already exists then just exit
	// 创建bridge虚拟设备
	if err := createBridgeInterface(bridgeName); err != nil {
		return fmt.Errorf("Error add bridge： %s, Error: %v", bridgeName, err)
	}

	// set bridge ip
	// 设置bridge设备的地址和路由
	ipRangePtr := *networkPtr.IpRangePtr
	ipRangePtr.IP = networkPtr.IpRangePtr.IP

	// 设置网桥的ip和路由
	if err := setInterfaceIP(bridgeName, ipRangePtr.String()); err != nil {
		return fmt.Errorf("Error assigning address: %s on bridge: %s with an error of: %v", ipRangePtr, bridgeName, err)
	}

	// 开启网桥
	if err := setInterfaceUP(bridgeName); err != nil {
		return fmt.Errorf("Error set bridge up: %s, Error:%v", bridgeName, err)
	}

	// setup iptables
	// 设置iptables的SNAT规则
	if err := setupIPTables(bridgeName, networkPtr.IpRangePtr); err != nil {
		return fmt.Errorf("Error setting iptables for %s: %v", bridgeName, err)
	}
	return nil
}

func (this *BridgeNetworkDriver) deleteBridge(networkPtr *Network) error {
	bridgeName := networkPtr.Name
	brLink, err := netlink.LinkByName(bridgeName)
	if err != nil {
		return fmt.Errorf("Getting link with name %s failed: %v", bridgeName, err)
	}

	if err := netlink.LinkDel(brLink); err != nil {
		return fmt.Errorf("Failed to remove bridge interface %s delete: %v", bridgeName, err)
	}
	return nil
}

// 创建bridge虚拟设备
func createBridgeInterface(bridgeName string) error {
	// 先检查是否已经存在了这个同名的bridge设备
	_, err := net.InterfaceByName(bridgeName)
	// 如果已经存在或者报错则返回: 创建错误
	if err == nil || !strings.Contains(err.Error(), "no such network interface") {
		return err
	}

	// 初始化一个netlink的link基础对象, link的名字即bridge虚拟设备的名字
	la := netlink.NewLinkAttrs()
	la.Name = bridgeName

	// 使用刚才创建的Link的属性创建netlink的bridge对象
	br := &netlink.Bridge{
		la,
	}
	// 调用netlink的linkadd方法,创建bridge虚拟网络设备
	// netlink的linkadd方法是用来创建虚拟网络设备的. 相当于 ip link add xxxx
	if err := netlink.LinkAdd(br); err != nil {
		return fmt.Errorf("Bridge creation failed for bridge %s: %v", bridgeName, err)
	}
	return nil
}

// 开启
// 设置网络接口为up状态, 网络设备只有设置成up状态后才能处理和转发请求
func setInterfaceUP(interfaceName string) error {
	iface, err := netlink.LinkByName(interfaceName)
	if err != nil {
		return fmt.Errorf("Error retrieving a link named [ %s ]: %v", iface.Attrs().Name, err)
	}

	// 通过netlink的LinkSetUp方法设置接口状态为up状态
	// 等价于 ip link set xxx up命令
	if err := netlink.LinkSetUp(iface); err != nil {
		return fmt.Errorf("Error enabling interface for %s: %v", interfaceName, err)
	}
	return nil
}

// 设置bridge和veth设备的ip地址和路由
// 设置一个网络接口的ip地址, 例如setInterfaceIP("testbridge","192.168.0.1/24")
func setInterfaceIP(name string, rawIP string) error {
	// 重试次数
	retries := 2
	var iface netlink.Link
	var err error
	for i := 0; i < retries; i++ {
		// 通过netlink的linkByName方法找到需要设置的网络接口
		iface, err = netlink.LinkByName(name)
		if err == nil {
			break
		}
		logrus.Debugf("error retrieving new bridge netlink link [ %s ]...retrying", name)
		time.Sleep(2 * time.Second)
	}
	if err != nil {
		return fmt.Errorf("Abandoning retrieving the new bridge link from netlink,Run[ ip link ] to troubleshoot the error : %v", err)
	}

	// 由于netlink.ParseIPNet是对net.ParseCIDR的一个封装,因此可以将net.ParseCIDR的返回值的ip和net整合
	// 返回值中的ipNet既包含了网段的信息:192.168.0.0/24, 也包含了原始的ip:192.168.0.1
	ipNet, err := netlink.ParseIPNet(rawIP)
	if err != nil {
		return err
	}

	logrus.Infof("setInterfaceIP => ipNet:%s", ipNet.String())

	// 通过netlink.AddrAdd给网络接口配置地址, 相当于ip addr add xxx的命令
	// 同时,如果配置了地址所在的网段信息, 例如:192.168.0.0/24, 还会配置路由表192.168.0.0/24转发到这个testbridge的网络接口上
	// 在宿主机上将192.168.0.0/24的网段请求路由到网桥上
	addr := &netlink.Addr{ipNet, "", 0, 0, nil}
	return netlink.AddrAdd(iface, addr)
}

// 设置iptables对应的bridge的MASQUERADE规则
// MASQUERADE策略可以将请求包中的源地址转换成一个网络设备地址，对namespace中发出的包需要通过MASQUERADE策略将这个namespace的虚拟ip转换成宿主机出口网卡的IP
// 就可以在namespace中访问宿主机外的网络了。
// 创建SNAT规则,只要是从这个网桥上出来的包,都会做源IP的转换,保证了容器经过宿主机访问宿主机外部网络的包将源IP转换成机器IP,从而能正确的送达和接收
func setupIPTables(bridgeName string, subnet *net.IPNet) error {
	// MASQUERADE就是针对动态IP而设计的，他的作用是，从服务器的网卡上，自动获取当前ip地址来做NAT
	// iptables -t nat -A POSTROUTING -s <subnet> ! -o <bridgeNarne> -] MASQUERADE
	iptablesCmd := fmt.Sprintf("-t nat -A POSTROUTING -s %s ! -o %s -j MASQUERADE", subnet.String(), bridgeName)
	cmd := exec.Command("iptables", strings.Split(iptablesCmd, " ")...)
	logrus.Infof("MASQUERADE cmd:%s", cmd.String())
	// 执行iptables命令
	output, err := cmd.Output()
	if err != nil {
		logrus.Errorf("iptables output, %v", output)
	}
	return err
}
